package cn.waleychain.exchange.service.impl.wallet.utils;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.Wallet;
import org.web3j.crypto.WalletFile;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.ObjectMapperFactory;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.JsonRpc2_0Web3j;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.Transfer;
import org.web3j.utils.Convert;
import org.web3j.utils.Convert.Unit;
import org.web3j.utils.Files;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.jsuportframework.util.JSUtils;
import com.jsuportframework.util.json.JSONObject;
import com.jsuportframework.util.json.JSONParser;

import cn.waleychain.exchange.core.constant.DDIC;
import cn.waleychain.exchange.core.logger.LoggerHelper;
import cn.waleychain.exchange.core.utils.BigDecimalUtils;
import cn.waleychain.exchange.core.utils.CalendarUtils;
import cn.waleychain.exchange.feign.CoinServiceFeign;
import cn.waleychain.exchange.model.CoinInfo;
import cn.waleychain.exchange.model.CoinWallet;
import cn.waleychain.exchange.model.WalletCollectTask;
import cn.waleychain.exchange.service.impl.wallet.listener.EthReceiveListenerTwo;
import cn.waleychain.exchange.service.wallet.CoinWalletService;

@Service
@PropertySource(value = { "classpath:application.yml" })
public class EthCoinClient {
	
	private static final Logger mLog = LoggerFactory.getLogger(DefaultCoinClient.class);

    @Autowired
    private CoinWalletService coinWalletService;
    @Autowired
    private CoinServiceFeign cionFeign;

    private static final String DEFAULT_PWD = "e12B121V112nnqnc22";
    public static final double TRANSFER_FEE = 5.0E-4D;

    @Value("${eth.keystore.path}")
    private String keystore;

    private static final String OPT_ADDR = "0x831a60552100c728e8b3d0398983d17eabf0c73b";
    private static final String OPT_KEY = "{\"version\":3,\"crypto\":{\"mac\":\"87bdbbf8b0b075bff7f9562c111b0d9a20ae11e2d6a6df992aba7a9df6b2cb30\",\"cipherparams\":{\"iv\":\"c6ce65bf499413b221d76c5e08e6f6d2\"},\"kdfparams\":{\"dklen\":32,\"r\":8,\"salt\":\"48674be5bd74d74e5609948ff9e840285eb69dead12e0b8b62b7def0b13224ef\",\"p\":1,\"n\":262144},\"cipher\":\"aes-128-ctr\",\"ciphertext\":\"1558ecffdce31d21397a40d585089cfa78dec9c4184dfd2e2af06f64b0437e22\",\"kdf\":\"scrypt\"},\"id\":\"946b89d2-110a-462c-b0d8-77b6f2596543\",\"address\":\"831a60552100c728e8b3d0398983d17eabf0c73b\"}";
    private static final String OPT_PWD = "Cheng1230";

//    private static final String SXF_OPT_ADDR = "0x34e7ae847b51912bdb303513417c04f436ef65a4";
    private static final String SXF_OPT_KEY = "{\"address\":\"34e7ae847b51912bdb303513417c04f436ef65a4\",\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"e7a658c911cccde5d40d408739135ef4\"},\"ciphertext\":\"bc6050168abdeccf66447cb14ba3a5c7cffa6b04e4c1abdd313b8dbc6722af85\",\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":65536,\"p\":1,\"r\":8,\"salt\":\"9a81d39121b7211a8ccfef5845188079ac5185f8fcc4798cdbc0282c4b462391\"},\"mac\":\"252bc879dfd5622b4dccc257bd558b1c50733ac0722884425e38327a76ccf90f\"},\"id\":\"01040329-c18f-4a9c-84e1-c2a703cb1780\",\"version\":3}";
    private static final String SXF_OPT_PWD = "Jc123456";

    public EthCoinClient() {
    }

    public BigDecimal getBalance(CoinInfo tradeCoin) {
        try {
            Web3j web3j = getCoinClient(tradeCoin);
            return Convert.fromWei(new BigDecimal((web3j.ethGetBalance(OPT_ADDR, DefaultBlockParameterName.LATEST).send()).getBalance()), Unit.ETHER);
        } catch (Exception e) {
        	LoggerHelper.printLogErrorNotThrows(mLog, e, e.getMessage());
            return new BigDecimal("-99");
        }
    }

    public BigDecimal getBalance(CoinInfo tradeCoin, String addr) {
        try {
            Web3j web3j = getCoinClient(tradeCoin);
            return Convert.fromWei(new BigDecimal((web3j.ethGetBalance(addr, DefaultBlockParameterName.LATEST).send()).getBalance()), Unit.ETHER);
        } catch (Exception e) {
        	LoggerHelper.printLogErrorNotThrows(mLog, e, e.getMessage());
            return new BigDecimal("-99");
        }
    }

    public boolean isValidAddress(CoinInfo tradeCoin, String addr) {
        return WalletUtils.isValidAddress(addr);
    }

    public int getTransactionConfirmed(CoinInfo tradeCoin, String txhash) {
        try {
            Web3j web3j = getCoinClient(tradeCoin);
            BigInteger latest = (web3j.ethBlockNumber().send()).getBlockNumber();
            BigInteger search = ((web3j.ethGetTransactionByHash(txhash).send()).getResult()).getBlockNumber();
            return latest.subtract(search).intValue();
        } catch (Exception e) {
        	LoggerHelper.printLogErrorNotThrows(mLog, e, e.getMessage());
            return 0;
        }
    }

    public String getNewAddress(CoinInfo tradeCoin, Long userId) throws Exception {
        if (userId != null && userId.longValue() != 0L && tradeCoin != null && tradeCoin.getCoinId() != 0L) {
            CoinWallet userEth = this.coinWalletService.fetchCoinWalletInfo(userId.longValue(), tradeCoin.getCoinId());
            if (userEth != null && JSUtils.ifStringNotEmpty(userEth.getAddress())) {
                return userEth.getAddress();
            } else {
                String address = null;
                String filename = null;
                try {
                    File file = new File(this.keystore);
                    if (!file.exists()) {
                        file.mkdirs();
                    }
                    filename = WalletUtils.generateFullNewWalletFile(DEFAULT_PWD, file);
                    String jsonStr = Files.readString(new File(this.keystore + filename));
                    JSONObject jsonObj = JSONParser.parseObject(jsonStr);
                    address = "0x" + jsonObj.getString("address");
                } catch (NoSuchProviderException | InvalidAlgorithmParameterException | CipherException | IOException | NoSuchAlgorithmException e) {
                    LoggerHelper.printLogErrorNotThrows(mLog, e, "get new eth address error:" + e.getMessage());
                    throw e;
                }

                userEth = new CoinWallet();
                userEth.setBalance(BigDecimalUtils.genInitValue());
                userEth.setFreeze(BigDecimalUtils.genInitValue());
                userEth.setRecharge(BigDecimalUtils.genInitValue());
                userEth.setWithdraw(BigDecimalUtils.genInitValue());
                userEth.setStatus(DDIC.CoinWalletStatus.NORMAL.id);
                userEth.setCreateTime(new Date());
                userEth.setModifiedTime(new Date());
                userEth.setUserId(userId);
                userEth.setCoinId(tradeCoin.getCoinId());
                userEth.setAddress(address);
                userEth.setKeystore(filename);
                userEth.setPassword(JSUtils.BASE64Encode(DEFAULT_PWD));
                
                this.coinWalletService.insertUserCoinWallet(userEth);
                return address;
            }
        } else {
            throw new IllegalArgumentException("userId or coinId is null!");
        }
    }

    /**
     * 发送手续费
     *
     * @param tradeCoin
     * @param userId
     * @param to
     * @param amount
     * @return
     * @throws Exception
     */
    public String sendSXFToAddress(CoinInfo tradeCoin, Long userId, String to, BigDecimal amount) throws Exception {
        if (!WalletUtils.isValidAddress(to)) {
            throw new Exception("Invalid dest address!");
        } else {
            Web3j web3j = getCoinClient(tradeCoin);

            try {
                ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
                WalletFile walletFile = (WalletFile) objectMapper.readValue(SXF_OPT_KEY, WalletFile.class);
                Credentials credentials = Credentials.create(Wallet.decrypt(SXF_OPT_PWD, walletFile));
                String txid = Transfer.sendFunds(web3j, credentials, to, amount, Convert.Unit.ETHER).send().getTransactionHash();
                if (JSUtils.ifStringEmpty(txid)) {
                    throw new Exception("txid is empty!");
                } else {
                    return txid;
                }
            } catch (Exception var11) {
                throw new Exception("Send transaction error", var11);
            }
        }
    }

    public String sendToAddress(CoinInfo tradeCoin, Long userId, String to, BigDecimal amount) throws Exception {
        if (!WalletUtils.isValidAddress(to)) {
            throw new Exception("Invalid dest address!");
        } else {
            Web3j web3j = getCoinClient(tradeCoin);

            try {
                ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
                WalletFile walletFile = (WalletFile) objectMapper.readValue(OPT_KEY, WalletFile.class);
                Credentials credentials = Credentials.create(Wallet.decrypt(OPT_PWD, walletFile));
                String txid = Transfer.sendFunds(web3j, credentials, to, amount, Convert.Unit.ETHER).send().getTransactionHash();
                if (JSUtils.ifStringEmpty(txid)) {
                    throw new Exception("txid is empty!");
                } else {
                    return txid;
                }
            } catch (Exception var11) {
                throw new Exception("Send transaction error", var11);
            }
        }
    }

    public String sendToCenter(CoinInfo tradeCoin, Long userId, String addr, BigDecimal amount) throws Exception {
        CoinWallet userEth = this.coinWalletService.fetchCoinWalletInfo(userId.longValue(), tradeCoin.getCoinId());
        if (userEth == null) {
            throw new Exception("Can not find user eth info!");
        } else {
            if (JSUtils.ifStringEmpty(addr)) {
                addr = OPT_ADDR;
            }

            if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0) {

                //amount = this.getBalance(tradeCoin, userEth.getAddress()).subtract(BigDecimal.valueOf(5.0E-4D));
                amount = this.getBalance(tradeCoin, userEth.getAddress()).subtract(EthCoinClient.getNowTimeEthGasPrice(tradeCoin));
            }

            if (amount.compareTo(BigDecimal.ZERO) < 0) {
                throw new Exception("Transfer Amount is Error!");
            } else {
                Web3j web3j = getCoinClient(tradeCoin);

                try {
                    Credentials credentials = WalletUtils.loadCredentials(JSUtils.BASE64Decode(userEth.getPassword()), new File(this.keystore + userEth.getKeystore()));
                    LoggerHelper.printLogInfo(mLog, "归账address:" + credentials.getAddress());

                    String transactionReceipt = Transfer.sendFunds(web3j, credentials, addr, amount, Convert.Unit.ETHER).send().getTransactionHash();
                    // TransactionReceipt transactionReceipt = (TransactionReceipt)Transfer.sendFundsAsync(web3j, credentials, addr, amount, Unit.ETHER).get();
                    // String txid = transactionReceipt.getTransactionHash();
                    if (JSUtils.ifStringEmpty(transactionReceipt)) {
                        throw new Exception("txid is empty!");
                    } else {
                        return transactionReceipt;
                    }
                } catch (Exception var10) {
                    var10.printStackTrace();
                    throw new Exception("Send transaction error", var10);
                }
            }
        }
    }

    public void addCollectTask(CoinInfo tradeCoin, Long userId, BigDecimal amount) throws Exception {
        BigDecimal transferAmount = amount.subtract(EthCoinClient.getNowTimeEthGasPrice(tradeCoin));//获取实时的手续费对应的eth价格
        if (BigDecimal.ZERO.compareTo(transferAmount) >= 0) {
            throw new Exception("Transfer amount is zero!");
        } else {
            WalletCollectTask task = new WalletCollectTask();
            task.setCoinId(tradeCoin.getCoinId());
            task.setFromUser(userId);
            task.setToAddr(OPT_ADDR);
            task.setAmount(transferAmount);
            task.setExecTime(CalendarUtils.addMinute(new Date(), 30));
            
            this.coinWalletService.createWalletCollectTask(task);
        }
    }


    public static BigDecimal getNowTimeEthGasPrice(CoinInfo tradeCoin) {
        Web3j web3j = null;
        try {
            web3j = EthCoinClient.getCoinClient(tradeCoin);
            BigDecimal price = new BigDecimal(web3j.ethGasPrice().send().getGasPrice().multiply(BigInteger.valueOf(21001L)));
            return price.divide(BigDecimal.TEN.pow(18));//获取实时的手续费对应的eth价格
        } catch (Exception e) {
            LoggerHelper.printLogErrorNotThrows(mLog, e, e.toString());
        }
        return BigDecimal.valueOf(0.001);
    }

    public void refreshAcceptor(CoinInfo eth, List<CoinInfo> tradeCoins) throws Exception {

        WalletAcceptorUtil.instance.upEthAcceptor(eth.getCoinId(), 
        		new EthReceiveListenerTwo(tradeCoins, eth.getCoinId(), eth.getName(), JSUtils.ifStringEmpty(eth.getLastblock()) ? 0 : Integer.parseInt(eth.getLastblock()), coinWalletService,cionFeign));
    }

    public static Web3j getCoinClient(CoinInfo tradeCoin) throws Exception {
        if (tradeCoin != null && !StringUtils.isEmpty(tradeCoin.getRpcIp()) && !StringUtils.isEmpty(tradeCoin.getRpcPort())) {
            String rpcUrl = "http://" + tradeCoin.getRpcIp() + ":" + tradeCoin.getRpcPort() + "/";
            Web3j web3j = new JsonRpc2_0Web3j(new HttpService(rpcUrl));
            return web3j;
        } else {
            throw new IllegalArgumentException("RPC no setting");
        }
    }
}
